﻿namespace TestApp.DataService
{
    using System;
    using System.Collections.Concurrent;
    using System.Diagnostics.Contracts;
    using System.Reactive.Disposables;
    using System.ServiceModel;
    using Contracts;
    using JetBrains.Annotations;

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    internal class DataService : IDataService
    {
        private readonly ICallbackProvider<IDataServiceCallback> _callbackProvider;
        private readonly Func<IDataSender> _dataSenderFactory;
        private readonly ISimpleLogger _logger;
        private readonly ConcurrentDictionary<Guid, IDataSender> _senders = new ConcurrentDictionary<Guid, IDataSender>();

        public DataService(
            [NotNull] ICallbackProvider<IDataServiceCallback> callbackProvider,
            [NotNull] Func<IDataSender> dataSenderFactory,
            [NotNull] ISimpleLogger logger)
        {
            Contract.Requires<ArgumentNullException>(callbackProvider != null);
            Contract.Requires<ArgumentNullException>(dataSenderFactory != null);
            Contract.Requires<ArgumentNullException>(logger != null);
            _callbackProvider = callbackProvider;
            _dataSenderFactory = dataSenderFactory;
            _logger = logger;
        }

        public void Ping(Guid subscriptionId)
        {
            IDataSender dataSender;
            if (_senders.TryGetValue(subscriptionId, out dataSender))
            {
                try
                {
                    dataSender.Check();
                }
                catch
                {
                    Unsubscribe(subscriptionId);
                }
            }
        }

        public Guid Subscribe(int index)
        {
            Contract.Requires<ArgumentOutOfRangeException>(index >=0 && index < DataEntityDto.EntityCount);
            return CreateSubscription(index);
        }

        public Guid SubscribeToAll()
        {
            return CreateSubscription();
        }

        public void Unsubscribe(Guid subscriptionId)
        {
            IDataSender dataSender;
            if (_senders.TryRemove(subscriptionId, out dataSender))
            {
                dataSender.Dispose();
                _logger.Write(string.Format("Unsubscribed. Totals: {0}", _senders.Count));
            }
        }

        public void Dispose()
        {
            new CompositeDisposable(_senders.Values).Dispose();
            _senders.Clear();
        }

        private Guid CreateSubscription(int? index = null)
        {
            var subscriptionId = Guid.NewGuid();
            _senders.GetOrAdd(subscriptionId, id =>
            {
                var dataSender = _dataSenderFactory();
                var anchors = new CompositeDisposable
                {
                    Disposable.Create(() => Unsubscribe(subscriptionId))
                };

                dataSender.Initialize(anchors, _callbackProvider.GetCallback(), index);
                return dataSender;
            });

            _logger.Write(string.Format("Subscribed. Totals: {0}", _senders.Count));
            return subscriptionId;
        }
    }
}
